<template>
  <render v-bind="props"></render>
</template>
<script setup lang="ts">
import { ref, watch, inject, onMounted, onBeforeUnmount, h, VNode } from 'vue'
type PROPS = {
  alt?: string
  formatOptions: {
    w: number
    h: number
    dpr?: number
    c?: boolean
    style?: string
  }
  loading?: boolean
  modspace?: string
  src: string
  usePicture?: boolean
}
const props = withDefaults(defineProps<PROPS>(), {
  alt: '',
  loading: true,
  usePicture: false,
  modspace: '',
})
const emits = defineEmits<{
  (e: 'load'): void
}>()
const enableAVIF = inject('enableAVIF', false) //是否使用avif格式
const imgSrcDefDomain = inject('imgSrcDefDomain', '') //图片源域名
const loaded = ref(false) //是否加载完成
const active = ref(false) //是否激活
const $el = ref<HTMLElement>() //图片元素
const template = ref<VNode>() //图片模板
const imgClassName = 'b-img__inner' //图片类名
let observer: IntersectionObserver | null = null //观察器，用于懒加载
const config: {
  dpr: number //设备像素比
  usePicture?: boolean //是否使用picture标签
  performance?: boolean //是否使用性能模式
} = {
  dpr: window.devicePixelRatio,
}
window.__BMG_TRACKER_COLLECT_EXTS__ = { avif: true, webp: true } //可支持图片格式，这里需要使用异步获取对应格式的图片，判断是否成功加载

const handleSrcChange = async (src: string) => {
  active.value = false
  loaded.value = false
  await Promise.resolve()
  // 添加监听器
  observer = new IntersectionObserver((changes) => {
    // 判断是否在可视区域内
    changes.forEach((change) => {
      if (change.isIntersecting) {
        const el = change.target
        if (el === $el.value) {
          active.value = true
          loaded.value = false
          template.value = createTemplate(
            src,
            Object.assign({}, props.formatOptions, {
              class: imgClassName,
              alt: props.alt,
              dpr: props.formatOptions.dpr || config.dpr,
              loading: props.loading,
            }),
            {
              tag:
                config.usePicture || props.usePicture ? 'picture' : undefined,
              onload: () => {},
              onerror: () => {},
              exts: { avif: enableAVIF },
              domain: imgSrcDefDomain,
              performance: false,
            }
          )
        }
      }
    })
  })
  $el.value && observer.observe($el.value)
  emits('load')
  loaded.value = true
}
interface CreateTemplateParams {
  w: number
  h: number
  class: string
  alt: string
  dpr: number
  loading: boolean
  dataset?: Record<string, any>
}
interface CreateTemplateOptions {
  tag?: string
  onload: () => void
  onerror: () => void
  exts: { avif: boolean }
  domain: string
  performance?: boolean
}
// 创建图片模板
const createTemplate = (
  src: string,
  params: CreateTemplateParams,
  options: CreateTemplateOptions
) => {
  let r
  const classList: string[] = []
  let className = ''
  if (params.class) {
    classList.push(params.class)
    className = classList.join(' ')
  }
  src = replaceUrlDomain(src, options.domain)
  const _useAvif = options?.exts?.avif
  const _performance = options?.performance
  const _usePictureTag =
    isUsableImageResource(src) && options?.tag
      ? 'picture' === options.tag
      : !!window?.HTMLPictureElement
  const _supportExit = getImageSupportExit(_performance && _useAvif)
  // 根据支持的格式及图片属性获取修饰后的图片资源
  const _decoratedSrc = decorateImageSrc(
    src,
    Object.assign({}, params, { ext: _supportExit })
  )
  let _ = true
  // 设置图片节点的属性dataset
  let dset: Record<string, string> = {}
  if ('object' == getValueTypeof(params.dataset)) {
    for (let k in params.dataset) {
      dset[`data-${k}`] = params.dataset[k]
    }
  }
  let b = _performance && _useAvif && 'avif' === _supportExit
  const imgNode = h(
    'img',
    Object.assign({
      src: _decoratedSrc,
      class: b || !_usePictureTag ? className : null,
      alt: params.alt || null,
      loading: params.loading ? 'lazy' : null,
      onload: _ ? options.onload : null,
      onerror: _ ? options.onerror : null,
      'data-onload': (_ && options.onload?.name) || null,
      'data-onerror': (_ && options.onerror?.name) || null,
    })
  )
  return b
    ? imgNode
    : _usePictureTag
    ? h('picture', { class: className || null }, [
        (_useAvif &&
          h('source', {
            type: 'image/avif',
            srcset: getImageNewSrcByExt(_decoratedSrc, 'avif'),
          })) ||
          '',
        (_supportExit === 'webp' &&
          h('source', {
            type: 'image/webp',
            srcset: getImageNewSrcByExt(_decoratedSrc, 'webp'),
          })) ||
          '',
        imgNode,
      ])
    : imgNode
}
const defaultDomainReg = /(s|i)\d.hdslb.com/ // 默认域名正则
// 替换图片源域名，前提是要符合默认域名才允许替换
const replaceUrlDomain = (url: string, domain: string) => {
  let n = url.match(defaultDomainReg)
  if (!domain) return url
  let r = n?.[0] || ''
  return r && r !== domain ? url.replace(defaultDomainReg, domain) : url
}
// 判断是否在浏览器环境
const isBrowser = () => {
  return 'undefined' == typeof window
}
//
// 判断是否是可用的图片资源
const isUsableImageResource = (e: string) => {
  // 判断是否是bfs资源
  let isBfs = !!/\/bfs\//.test(e)
  // 判断是否是图片
  let isImage = !!/\.(jpeg|jpg|png|webp|gif|avif)$/.test(e)
  return isBfs && isImage
}
// 获取图片支持的格式
const getImageSupportExit = (n: boolean | undefined) => {
  //返回使用哪种显示优先
  // avif 、 webp
  return ''
}
// 装饰图片源
const decorateImageSrc = (src: string, op: Record<string, any>) => {
  const _dpr: number | undefined = window?.devicePixelRatio
  let n = op || { c: true }
  if ('string' != typeof src) return ''
  if (!isUsableImageResource(src)) return src
  // 如果src中包含@，可能已经处理过
  if (src.indexOf('@') > -1) {
    // 如果使用缓存，直接返回
    if (n.cache) return src
    // 否则获取初始图片地址
    src = src.split('@')[0]
  }
  src = src.replace('pre-', '')
  const dpr = n.dpr || (isBrowser() ? 2 : Math.floor(_dpr ? _dpr : 2) || 1)
  const srcExStr: string[] = [] //图片修饰，生成最终的图片资源
  n.w && isNumber(n.w) && srcExStr.push(`${Math.floor(n.w * dpr)}w`)
  n.h && isNumber(n.h) && srcExStr.push(`${Math.floor(n.h * dpr)}h`)
  n.e && srcExStr.push(`${n.e}e`)
  n.c && srcExStr.push('1c')
  n.a &&
    srcExStr.push(
      `${[
        parseInt(n.a.x),
        parseInt(n.a.y),
        parseInt(n.a.w) * dpr,
        parseInt(n.a.h) * dpr,
      ].join('-')}a`
    )
  n.p && isNumber(n.p) && srcExStr.push(`${n.p}p`)
  n.s && srcExStr.push('1s')
  n.q && isNumber(n.q) && srcExStr.push(`${n.q}q`)
  n.style && srcExStr.push(`!${n.style}`)
  let a = src
  let o = ''
  n.ext && ['webp', 'avif'].includes(n.ext) && (o = n.ext)
  return getValueTypeof.length || o
    ? `${a?.replace(/^http(s)?:/, '') || ''}@${srcExStr.join('_')}${
        o ? `.${o}` : ''
      }`
    : src
}
//  判断是否为（Symbol若支持）对象
let getValueTypeof = (e: any) => {
  return 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator
    ? (function (e) {
        return typeof e
      })(e)
    : (function (e) {
        return e &&
          'function' == typeof Symbol &&
          e.constructor === Symbol &&
          e !== Symbol.prototype
          ? 'symbol'
          : typeof e
      })(e)
}
const getImageNewSrcByExt = (originalSrc: string, ext: string) => {
  return -1 === originalSrc.indexOf('@')
    ? `${originalSrc}@.${ext}` //''.concat(e, '@.').concat(t)
    : `${(originalSrc = originalSrc.replace(
        new RegExp('.(avif|webp)$'),
        ''
      ))}.${ext}` //''.concat((e = e.replace(new RegExp('.(avif|webp)$'), '')), '.').concat(t)
}
const isNumber = (e: any) => {
  return 'number' == typeof e && (0 | e) === e
}
// 向下取整获取数字，非数字返回0
const parseInt = (e: any) => {
  return 'number' != typeof e || isNaN(e) ? 0 : Math.floor(e) || 0
}
onBeforeUnmount(() => {
  observer?.disconnect()
})
watch(
  () => props.src,
  (newVal) => {
    handleSrcChange(newVal)
  },
  { immediate: true }
)

const render = (props: PROPS) => {
  return h(
    'div',
    {
      class: {
        'b-img': true,
        sleepy: props.loading && !loaded.value,
      },
      // ref 必须使用函数形式，否则无法获取到元素
      ref: (el) => {
        $el.value = el as HTMLElement
      },
    },
    [template.value] // vue3直接使用[]传入子节点，不需要使用domProps传入
  )
}
</script>
<style lang="scss">
.b-img {
  background-color: #f1f2f3;
  height: 100%;
  position: relative;
  &__inner {
    width: 100%;
    height: 100%;
    display: inline-block;
    vertical-align: middle;
    img {
      display: block;
      width: 100%;
      height: 100%;
      object-fit: inherit;
    }
  }
  img,
  picture {
    border-radius: inherit;
  }
}
</style>
